﻿using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Security;

namespace OutOfUnity
{
    public class KcpSocket : ISocket
    {
        private Socket socket_ = null;
        private KCP kcp_ = null;
        private string error_ = "";
        private IPEndPoint remoteEndPoint_ = null;
        private bool connected_ = false;
        private ConcurrentQueue<byte[]> recvPacket_ = new ConcurrentQueue<byte[]>();
        private byte[] tempRecvBuffer_ = new byte[Config.RECV_BUF_SIZE];
        private static readonly DateTime utc_time = new DateTime(1970, 1, 1);
        public static UInt32 iclock()
        {
            return (UInt32)(Convert.ToInt64(DateTime.UtcNow.Subtract(utc_time).TotalMilliseconds) & 0xffffffff);
        }
        private void receiveFrom()
        {
            if (kcp_ == null)
            {
                return;
            }
            if (socket_ == null)
            {
                return;
            }
            var ep = (EndPoint)new IPEndPoint(IPAddress.Any, 0);
            int bytes = 0;
            while (true)
            {
                try
                {
                    bytes = socket_.ReceiveFrom(tempRecvBuffer_, SocketFlags.None, ref ep);
                }
                catch (SocketException)
                {
                    return;
                }
                catch (ObjectDisposedException e)
                {
                    error_ = e.Message;
                    connected_ = false;
                    return;
                }
                catch (SecurityException e)
                {
                    error_ = e.Message;
                    connected_ = false;
                    return;
                }
                if (bytes == 0)
                {
                    return;
                }
                else
                {
                    kcp_.Input(tempRecvBuffer_, bytes);
                }
            }            
        }
        public void update()
        {
            if (kcp_ == null)
            {
                return;
            }
            if (socket_ != null)
            {
                receiveFrom();
                var recvBytes = kcp_.Recv(tempRecvBuffer_);
                if (recvBytes > 0)
                {
                    byte[] stream = new byte[recvBytes];
                    Array.Copy(tempRecvBuffer_, stream, recvBytes);
                    recvPacket_.Enqueue(stream);
                }
            }
            kcp_.Update(iclock());
        }
        public bool connect(String ip, int port)
        {
            kcp_ = new OutOfUnity.KCP(Config.KCPCONV, (byte[] buf, int size) =>
            {
                try
                {
                    var bytes = socket_.SendTo(buf, size, SocketFlags.None, remoteEndPoint_);
                    if (bytes <= 0)
                    {
                        connected_ = false;
                        return;
                    }
                }
                catch(SocketException e)
                {
                    error_ = e.Message;
                    connected_ = false;
                }
                catch(ObjectDisposedException e)
                {
                    error_ = e.Message;
                    connected_ = false;
                }
            });
            kcp_.NoDelay(Config.KCP_NODELAY_NODELAY, Config.KCP_NODELAY_INTERVAL, Config.KCP_NODEALY_RESEND, Config.KCP_NODELAY_NC);
            kcp_.SetMtu(Config.MTU);
            socket_ = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            socket_.Bind(new IPEndPoint(IPAddress.Any, 0));
            socket_.ReceiveTimeout = Config.UDP_RECV_TIMEOUT;
            socket_.ReceiveBufferSize = Config.RECV_BUF_SIZE;
            socket_.SendBufferSize = Config.SEND_BUF_SIZE;
            remoteEndPoint_ = new IPEndPoint(IPAddress.Parse(ip), port);
            connected_ = true;
            return true;
        }
        public bool close()
        {
            if ((socket_ == null) || (kcp_ == null))
            {
                return false;
            }
            socket_.Close();
            socket_ = null;
            kcp_ = null;
            remoteEndPoint_ = null;
            return true;
        }
        public int send(byte[] buffer, int size)
        {
            if (!connected_)
            {
                return -1;
            }
            if ((socket_ == null) || (kcp_ == null))
            {
                return -1;
            }
            if (0 > kcp_.Send(buffer, size))
            {
                return -1;
            }
            return size;
        }
        public int receive(byte[] buffer)
        {
            if (!connected_)
            {
                return -1;
            }
            if (socket_ == null)
            {
                return -1;
            }
            byte[] packet = null;
            if (recvPacket_.TryDequeue(out packet))
            {
                Array.Copy(packet, buffer, packet.Length);
                return packet.Length;
            }
            return 0;            
        }
        public String getError()
        {
            return error_;
        }
        public bool isConnect()
        {
            return connected_;
        }
    }
}
